/*
 * Copyright 2008 Max Kugland
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.splink.library.reflection {
	import flash.system.ApplicationDomain;
	import flash.utils.describeType;

	/**
	 *  <code>ClazzInspector</code> enables the programming concept of reflection. It shows public properties and 
	 *  methods of a given <code>Class</code>, but will not show  properties and methods that are  private, package 
	 *  internal or in custom namespaces, because it is built on top of actionscripts <code>describeType</code>.
	 * 
	 * @author Max Kugland
	 */
	public class ClazzInspector {
		private var _clazz : Class;
		private var _xml : XML;
		
		/**
		 * Inspects a <code>Class</code>. If you don't have a <code>Class</code>  at hand you can use the static
		 * helper methods <code>inspectClazzByInstance</code> and  <code>inspectClazzByName</code>.
		 * 
		 * @param clazz the class to be inspected
		 */
		public function ClazzInspector(clazz : Class) {
			_xml = describeType(_clazz = clazz);
		}
		
		/**
		 * Looks up a class by its instance and returns a new <code>ClazzInspector</code>.
		 * 
		 * @param instance an instance of the class to be inspected
		 * @throws Error if the class for the provided instance could not been resolved 
		 */
		public static function inspectClazzByInstance(instance : Object) : ClazzInspector {
			try {
				return new ClazzInspector(instance["constructor"] as Class);
			} catch (e : Error) {
				throw new Error("Could not resolve class for " + instance);
			}

			return null;
		}

		/**
		 * Looks up a class by its name and returns a new <code>ClazzInspector</code>.
		 * 
		 * @param name the name of the class to be inspected
		 * @throws Error if the class for the provided name could not been resolved 
		 */
		public static function inspectClazzByName(name : String, domain : ApplicationDomain = null) : ClazzInspector {
			try {
				if(domain == null) {
					domain = ApplicationDomain.currentDomain;
				}
				return new ClazzInspector(domain.getDefinition(name) as Class);
			} catch (e : Error) {
				throw new Error("Could not resolve class for " + name);
			}
			return null;
		}

		/**
		 * @return an Array of the names of the interfaces implemented by the inspected class
		 */
		public function get interfaces() : Array {
			return getArray(_xml["factory"]["implementsInterface"], "type");
		}

		/**
		 * @return an array of <code>MetaData</code> objects representing the classes metadata 
		 * if present, otherwise returns an empty array
		 */
		public function get metaData() : Array {
			return createMetaData(_xml["factory"]["metadata"]);
		}

		/**
		 * @return a <code>Method</code> object containing information regarding the constructor
		 */
		public function get constructor() : Method {
			return new Method(_clazz, clazzname, null, [], false);
		}

		/**
		 * @return an array of <code>Accessor</code> methods
		 * @see org.splink.library.reflection.Accessor
		 */
		public function get accessors() : Array {
			return getAccs(_xml["factory"]["accessor"], false);
		}

		/**
		 * @return an array of static <code>Accessor</code> methods
		 * @see org.splink.library.reflection.Accessor
		 */
		public function get staticAccessors() : Array {
			return getAccs(_xml["accessor"], true);
		}

		/**
		 * @return an array of <code>Variable</code> objects representing all constants 
		 * within the inspected class
		 * @see org.splink.library.reflection.Variable
		 */
		public function get constants() : Array {
			return getConsts(_xml["factory"]["constant"], false);
		}

		/**
		 * @return an array of <code>Variable</code> objects representing all public constants within 
		 * the inspected class
		 * @see org.splink.library.reflection.Variable
		 */
		public function get staticConstants() : Array {
			return getConsts(_xml["constant"], true);
		}

		/**
		 * @return an array of <code>Variable</code> objects representing all public static variables 
		 * within the inspected class 
		 * @see org.splink.library.reflection.Variable
		 */
		public function get staticVariables() : Array {
			return getVars(_xml["variable"], true);
		}

		/**
		 * @return an array of <code>Variable</code> objects representing all public variables within 
		 * the inspected class
		 * @see org.splink.library.reflection.Variable
		 */
		public function get variables() : Array {
			return getVars(_xml["factory"]["variable"], false);
		}

		/**
		 * @return an array of <code>Method</code> objects representing all public methods within the 
		 * inspected class
		 * @see org.splink.library.reflection.Method
		 */
		public function get methods() : Array {
			var ar : Array = [];
			var list : XMLList = _xml["factory"]["method"];
			for each (var item:XML in list) {
				ar.push(new Method(_clazz, item.@name, item.@returnType, getParams(item), false, createMetaData(item["metadata"])));
			}
			return ar;
		}

		/**
		 * @return an array of <code>Method</code> objects representing all public static methods 
		 * within the inspected class
		 * @see org.splink.library.reflection.Method
		 */
		public function get staticMethods() : Array {
			var ar : Array = [];
			var list : XMLList = _xml["method"];
			for each (var item:XML in list) {
				ar.push(new Method(_clazz, item.@name, item.@returnType, getParams(item), true, createMetaData(item["metadata"])));
			}
			return ar;
		}

		private function createMetaData(metaData : XMLList) : Array {
			if(metaData == null)
				return null;

			var metaDataAr : Array = new Array;
			for each (var data : XML in metaData) {
				var name : String = data.@name;
				if(name == "")
					continue;
				var argsAr : Array = new Array;
				for each (var node : * in data["arg"]) {
					argsAr.push(new MetaDataArgument(String(node.@key), node.@value));
				}
				metaDataAr.push(new MetaData(name, argsAr));
			}

			return metaDataAr;
		}

		/**
		 * @return an array containing the inheritance chain
		 */
		public function get inheritanceChain() : Array {
			return getArray(_xml["factory"]["extendsClass"], "type");
		}

		/**
		 * @return the superclass of the inspected class
		 */
		public function get superClass() : String {
			return getArray(_xml["factory"]["extendsClass"], "type")[0];
		}

		/**
		 * @return true if the class is dynamic, otherwise false is  returned
		 */
		public function get isDynamic() : Boolean {
			return _xml.@isDynamic == "true";
		}

		/**
		 * @return true if the class is declared as final otherwise returns false
		 */
		public function get isFinal() : Boolean {
			return _xml.@isFinal == "true";
		}

		/**
		 * @return true if the class is declared as static otherwise returns false
		 */
		public function get isStatic() : Boolean {
			return _xml.@isStatic == "true";
		}

		/**
		 * @return the name of the inspected class
		 */
		public function get clazzname() : String {
			return _xml.@name;
		}

		private function getAccs(list : XMLList, isStatic : Boolean) : Array {
			var ar : Array = [];
			for each (var item:XML in list) {
				ar.push(new Accessor(_clazz, item.@name, item.@access, item.@type, isStatic, createMetaData(item["metadata"])));
			}
			return ar;
		}

		private function getConsts(list : XMLList, isStatic : Boolean) : Array {
			var ar : Array = [];
			for each (var item:XML in list) {
				ar.push(new Variable(_clazz, item.@name, item.@type, isStatic));
			}
			return ar;
		}

		private function getVars(list : XMLList, isStatic : Boolean) : Array {
			var ar : Array = [];
			for each (var item:XML in list) {
				ar.push(new Variable(_clazz, item.@name, item.@type, isStatic, createMetaData(item["metadata"])));
			}
			return ar;
		}

		private function getParams(method : XML) : Array {
			var ar : Array = [];
			var list : XMLList = method["parameter"];
			for each (var item:XML in list) {
				ar.push(new Parameter(item.@index, item.@type, item.@optional == "true"));
			}
			return ar;
		}

		private function getArray(list : XMLList, attribute : String) : Array {
			var ar : Array = [];
			for each (var item:XML in list) {
				ar.push(item["@" + attribute]);
			}
			return ar;
		}

		/**
		 * @return the inspected <code>Class</code>
		 */
		public function get clazz() : Class {
			return _clazz;
		}
	}
}